<script src="https://cdn.plot.ly/plotly-2.34.0.min.js"></script>

<style>
  .generate-button {
    color: #fff !important;
    background-color: #00a9ce !important;
    box-shadow: none;
    max-width: 100%;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
    display: block;
    border-radius: 0.25rem;
    border: 1px solid transparent;
    padding: 0.375rem 0.75rem;
    font-size: 1rem;
    font-weight: 400;
    line-height: 1.5;
    margin-top: 1.5rem !important;
  }
  .form-select {
    width: 9rem;
    padding-top: 4px;
    padding-bottom: 4px;
    border-top-width: 2px;
    border-bottom-width: 2px;
    border-right-width: 2px;
    border-left-width: 2px;
  }
  .form-control-sm {
    border-color: darkgray;
  }

  /* Mobile-friendly responsive grid */
  @media (max-width: 767px) {
    #gui-control {
      max-width: 100% !important;
      padding: 0 15px;
    }

    /* Stack all form columns vertically on mobile */
    #gui-control .col {
      margin-bottom: 1rem;
      flex: 0 0 100% !important;
      max-width: 100% !important;
    }

    /* Center form elements */
    #gui-control .mx-auto {
      margin-left: auto !important;
      margin-right: auto !important;
    }

    /* Hide empty columns on mobile using Bootstrap's responsive utilities */
    .d-md-flex.d-none {
      display: none !important;
    }

    #event-layout {
      max-width: 100% !important;
      height: 150px !important;
    }

    #gui-table {
      max-width: 100% !important;
      min-width: auto !important;
    }
  }

  /* Tooltip styles */
  .tooltip-label {
    position: relative;
    cursor: help;
    border-bottom: 1px dotted #666;
  }

  .tooltip-label::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: 125%;
    left: 50%;
    transform: translateX(-50%);
    background-color: #333;
    color: white;
    padding: 8px 12px;
    border-radius: 4px;
    font-size: 0.875rem;
    font-weight: normal;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s, visibility 0.3s;
    transition-delay: 0s;
    z-index: 1000;
    max-width: 300px;
    white-space: normal;
    width: max-content;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
  }

  .tooltip-label::before {
    content: "";
    position: absolute;
    bottom: 115%;
    left: 50%;
    transform: translateX(-50%);
    border: 5px solid transparent;
    border-top-color: #333;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s, visibility 0.3s;
    transition-delay: 0s;
    z-index: 1000;
  }

  .tooltip-label:hover::after,
  .tooltip-label:hover::before {
    opacity: 1;
    visibility: visible;
    transition-delay: 0.8s; /* 800ms delay before appearing */
  }

  /* Mobile responsive tooltips */
  @media (max-width: 768px) {
    .tooltip-label::after {
      font-size: 0.75rem;
      padding: 6px 8px;
      max-width: 250px;
      bottom: 130%;
      left: 0;
      transform: none;
      right: auto;
    }
    .tooltip-label::before {
      left: 20px;
      transform: translateX(-50%);
      bottom: 120%;
    }
  }

  /* Touch devices - tooltip states managed by JavaScript */
  .tooltip-label.tooltip-active::after,
  .tooltip-label.tooltip-active::before {
    opacity: 1 !important;
    visibility: visible !important;
    transition-delay: 0s !important;
  }

  /* Accessibility improvements */
  .form-control-sm:focus,
  .form-select:focus {
    outline: 2px solid #00a9ce;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px rgba(0, 169, 206, 0.2);
  }

  .visually-hidden {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
  }

  /* High contrast mode support */
  @media (prefers-contrast: high) {
    .tooltip-label {
      border-bottom: 2px solid;
    }
    .tooltip-label::after {
      border: 1px solid;
    }
  }
</style>

<div id="gui-top">
  <!-- Screen reader announcement area -->
  <div
    id="sr-announcements"
    aria-live="polite"
    aria-atomic="true"
    class="visually-hidden"
  ></div>

  <div class="row justify-content-md-center" style="width: 100%">
    <form
      id="gui-control"
      style="max-width: 750px"
      aria-labelledby="form-title"
    >
      <h2 id="form-title" class="visually-hidden">
        BIS Configuration Parameters
      </h2>
      <div class="row">
        <div class="col d-flex">
          <div class="mx-auto d-block">
            <label
              for="inNse"
              class="tooltip-label"
              data-tooltip="Number of Subevents - The total number of subevents in each interval of each BIS in the BIG"
              >NSE</label
            ><br />
            <input
              type="number"
              class="form-control-sm"
              id="inNse"
              min="1"
              max="15"
              value="6"
              style="width: 9rem"
              aria-describedby="nse-desc"
              aria-label="Number of Subevents (NSE)"
            />
            <div id="nse-desc" class="visually-hidden">
              Number of Subevents - The total number of subevents in each
              interval of each BIS in the BIG. Valid range: 1 to 15.
            </div>
          </div>
        </div>

        <div class="col d-flex">
          <div class="mx-auto d-block">
            <label
              for="inBn"
              class="tooltip-label"
              data-tooltip="Burst Number - The number of new payloads in each interval for each BIS"
              >BN</label
            ><br />
            <input
              type="number"
              class="form-control-sm"
              id="inBn"
              min="1"
              max="7"
              value="2"
              style="width: 9rem"
              aria-describedby="bn-desc"
              aria-label="Burst Number (BN)"
            />
            <div id="bn-desc" class="visually-hidden">
              Burst Number - The number of new payloads in each interval for
              each BIS. Valid range: 1 to 7.
            </div>
          </div>
        </div>

        <div class="col d-flex">
          <div class="mx-auto d-block">
            <label
              for="inIrc"
              class="tooltip-label"
              data-tooltip="Immediate Repetition Count - The number of times the scheduled payloads are transmitted in a given event"
              >IRC</label
            ><br />
            <input
              type="number"
              class="form-control-sm"
              id="inIrc"
              min="1"
              max="15"
              value="2"
              style="width: 9rem"
              aria-describedby="irc-desc"
              aria-label="Immediate Repetition Count (IRC)"
            />
            <div id="irc-desc" class="visually-hidden">
              Immediate Repetition Count - The number of times the scheduled
              payloads are transmitted in a given event. Valid range: 1 to 15.
            </div>
          </div>
        </div>

        <div class="col d-flex">
          <div class="mx-auto d-block">
            <label
              for="inPto"
              class="tooltip-label"
              data-tooltip="Pre Transmission Offset - Offset used for pre-transmissions"
              >PTO</label
            ><br />
            <input
              type="number"
              class="form-control-sm"
              id="inPto"
              min="0"
              max="15"
              value="2"
              style="width: 9rem"
              aria-describedby="pto-desc"
              aria-label="Pre Transmission Offset (PTO)"
            />
            <div id="pto-desc" class="visually-hidden">
              Pre Transmission Offset - Offset used for pre-transmissions. Valid
              range: 0 to 15.
            </div>
          </div>
        </div>
      </div>

      <div class="row">
        <div class="col d-flex">
          <div class="mx-auto d-block">
            <label
              for="inNumBis"
              class="tooltip-label"
              data-tooltip="Number of BIS - The number of BIS in the BIG"
              >Num BIS</label
            ><br />
            <input
              type="number"
              class="form-control-sm"
              id="inNumBis"
              min="1"
              max="15"
              value="1"
              style="width: 9rem"
              aria-describedby="numbis-desc"
              aria-label="Number of Broadcast Isochronous Streams"
            />
            <div id="numbis-desc" class="visually-hidden">
              Number of Broadcast Isochronous Streams. Valid range: 1 to 15.
            </div>
          </div>
        </div>

        <div class="col d-flex">
          <div class="mx-auto d-block">
            <label
              for="inMaxPdu"
              class="tooltip-label"
              data-tooltip="Maximum PDU size in bytes"
              >Max. PDU [bytes]</label
            ><br />
            <input
              type="number"
              class="form-control-sm"
              id="inMaxPdu"
              min="1"
              max="251"
              value="130"
              style="width: 9rem"
              aria-describedby="maxpdu-desc"
              aria-label="Maximum Protocol Data Unit size"
            />
            <div id="maxpdu-desc" class="visually-hidden">
              Maximum Protocol Data Unit size in bytes. Valid range: 1 to 251.
            </div>
          </div>
        </div>

        <div class="col d-flex">
          <div class="mx-auto d-block">
            <label
              for="inIsoInterval"
              class="tooltip-label"
              data-tooltip="The time, in microseconds, between consecutive BIG anchor points"
              >ISO Interval [us]</label
            ><br />
            <input
              type="number"
              class="form-control-sm"
              id="inIsoInterval"
              min="5000"
              max="4000000"
              value="20000"
              step="1250"
              style="width: 9rem"
              aria-describedby="isointerval-desc"
              aria-label="ISO Interval in microseconds"
            />
            <div id="isointerval-desc" class="visually-hidden">
              The time between consecutive BIG anchor points in microseconds.
              Valid range: 5000 to 4000000, step: 1250.
            </div>
          </div>
        </div>

        <div class="col d-flex">
          <div class="mx-auto d-block">
            <label
              for="inSduInterval"
              class="tooltip-label"
              data-tooltip="The interval, in microseconds, of periodic SDUs"
              >SDU Interval [us]</label
            ><br />
            <input
              type="number"
              class="form-control-sm"
              id="inSduInterval"
              min="255"
              max="1048575"
              value="20000"
              style="width: 9rem"
              aria-describedby="sduinterval-desc"
              aria-label="SDU Interval in microseconds"
            />
            <div id="sduinterval-desc" class="visually-hidden">
              The interval, in microseconds, of periodic Service Data Units.
              Valid range: 255 to 1048575.
            </div>
          </div>
        </div>
      </div>

      <div class="row">
        <div class="col d-flex">
          <div class="mx-auto d-block">
            <label
              for="inPhy"
              class="tooltip-label"
              data-tooltip="Physical Layer configuration"
              >PHY</label
            ><br />
            <select
              class="form-select"
              id="inPhy"
              aria-label="Physical Layer Selection"
              aria-describedby="phy-desc"
              style="width: 9rem"
            >
              <option selected value="1">LE 1M</option>
              <option value="2">LE 2M</option>
              <option value="3">LE Coded</option>
            </select>
            <div id="phy-desc" class="visually-hidden">
              Physical Layer configuration. Options: LE 1M, LE 2M, or LE Coded.
            </div>
          </div>
        </div>

        <div class="col d-flex">
          <div class="mx-auto d-block">
            <label
              for="inFraming"
              class="tooltip-label"
              data-tooltip="Framing mode configuration"
              >Framing</label
            ><br />
            <select
              class="form-select"
              id="inFraming"
              aria-label="Framing Mode Selection"
              aria-describedby="framing-desc"
              style="width: 9rem"
            >
              <option selected value="0">Unframed</option>
              <option value="1">Framed</option>
            </select>
            <div id="framing-desc" class="visually-hidden">
              Framing mode configuration. Options: Unframed or Framed.
            </div>
          </div>
        </div>

        <div class="col d-flex">
          <div class="mx-auto d-block">
            <label
              for="inPacking"
              class="tooltip-label"
              data-tooltip="Packing mode configuration"
              >Packing</label
            ><br />
            <select
              class="form-select"
              id="inPacking"
              aria-label="Packing Mode Selection"
              aria-describedby="packing-desc"
              style="width: 9rem"
            >
              <option selected value="0">Sequential</option>
              <option value="1">Interleaved</option>
            </select>
            <div id="packing-desc" class="visually-hidden">
              Packing mode configuration. Options: Sequential or Interleaved.
            </div>
          </div>
        </div>

        <div class="col d-flex d-md-flex d-none">
          <div class="mx-auto d-block"></div>
        </div>
      </div>
    </form>
  </div>
  <div class="row justify-content-md-center" style="width: 100%">
    <section
      id="gui-table"
      style="max-width: 750px; min-width: 280px"
      aria-labelledby="results-title"
      aria-live="polite"
    >
      <h3 id="results-title" class="visually-hidden">
        Calculation Results Table
      </h3>
    </section>
  </div>

  <div
    class="row justify-content-md-center"
    id="gui-events"
    style="width: 100%"
  >
    <figure
      id="event-layout"
      style="max-width: 850px; height: 200px"
      aria-labelledby="chart-title"
      aria-describedby="chart-desc"
    >
      <h3 id="chart-title" class="visually-hidden">
        BIS Event Timeline Visualization
      </h3>
      <div id="chart-desc" class="visually-hidden">
        Interactive chart showing the timing and sequence of BIS events based on
        the configured parameters.
      </div>
    </figure>
  </div>
</div>

<script>
  // Accessibility: Track focus and announcements
  let lastAnnouncementTime = 0;

  function announceToScreenReader(message, priority = "polite") {
    const now = Date.now();
    // Throttle announcements to avoid overwhelming screen readers
    if (now - lastAnnouncementTime < 1000) return;

    const announcer = document.getElementById("sr-announcements");
    announcer.setAttribute("aria-live", priority);
    announcer.textContent = message;
    lastAnnouncementTime = now;

    // Clear announcement after 3 seconds
    setTimeout(() => {
      if (announcer.textContent === message) {
        announcer.textContent = "";
      }
    }, 3000);
  }

  // Add keyboard navigation support
  document.getElementById("gui-control").addEventListener("keydown", (e) => {
    // Allow escape to clear focus
    if (e.key === "Escape") {
      document.activeElement.blur();
    }
  });

  // Touch device tooltip management
  function setupTooltipToggle() {
    const tooltipLabels = document.querySelectorAll(".tooltip-label");

    tooltipLabels.forEach((label) => {
      label.addEventListener("click", (e) => {
        // Only handle clicks on touch devices or when explicitly requested
        if (
          !window.matchMedia("(hover: none)").matches &&
          e.pointerType !== "touch"
        ) {
          return;
        }

        e.preventDefault();
        e.stopPropagation();

        // Close all other tooltips
        tooltipLabels.forEach((otherLabel) => {
          if (otherLabel !== label) {
            otherLabel.classList.remove("tooltip-active");
          }
        });

        // Toggle current tooltip
        label.classList.toggle("tooltip-active");
      });
    });

    // Close tooltips when clicking elsewhere
    document.addEventListener("click", (e) => {
      if (!e.target.closest(".tooltip-label")) {
        tooltipLabels.forEach((label) => {
          label.classList.remove("tooltip-active");
        });
      }
    });

    // Close tooltips on escape key
    document.addEventListener("keydown", (e) => {
      if (e.key === "Escape") {
        tooltipLabels.forEach((label) => {
          label.classList.remove("tooltip-active");
        });
      }
    });
  }

  // Initialize tooltip toggle functionality
  setupTooltipToggle();

  document
    .getElementById("gui-control")
    .querySelectorAll("select, input")
    .forEach((el) => {
      el.addEventListener("input", () => {
        showEvents();
      });

      // Accessibility: Announce validation errors
      el.addEventListener("blur", () => {
        const value = parseFloat(el.value);
        const min = parseFloat(el.min);
        const max = parseFloat(el.max);

        if (isNaN(value)) {
          announceToScreenReader(
            `${el.getAttribute("aria-label")} requires a valid number.`,
            "assertive"
          );
        } else if (value < min || value > max) {
          announceToScreenReader(
            `${el.getAttribute(
              "aria-label"
            )} value ${value} is outside valid range ${min} to ${max}.`,
            "assertive"
          );
        }
      });
    });

  showEvents();

  function getDefaultTrace() {
    return {
      x: [],
      y: [],
      type: "bar",
      text: "",
      textposition: "auto",
      insidetextfont: {
        color: "black",
      },
      hoverlabel: {
        font: {
          color: "black",
        },
        bgcolor: "rgb(231, 242, 250)",
      },
      hoverinfo: "text",
      hovertext: [],
      marker: {
        color: "rgb(158,202,225)",
        line: {
          width: 1.5,
        },
      },
    };
  }

  function setSubeventTrace(
    traces,
    annotations,
    numberOfPdus,
    subeventNo,
    nse,
    bn,
    irc,
    pto,
    fontFamily
  ) {
    const rd = 117 / numberOfPdus;
    const gd = 117 / numberOfPdus;
    const bd = 30 / numberOfPdus;
    const currentEvent = Math.floor(subeventNo / nse);
    const group = Math.floor((subeventNo % nse) / bn);
    let pduEvent = currentEvent;
    if (group >= irc) {
      const ptoGroup = group - irc + 1;
      pduEvent += ptoGroup * pto;
    }

    const pdu = pduEvent * bn + (subeventNo % bn);
    traces[pdu].y.push(1);
    traces[pdu].x.push(currentEvent + subeventNo);
    traces[pdu].text = `${pdu}`;
    traces[pdu].marker.color = `rgb(${Math.floor(147 - pdu * rd)},${Math.floor(
      232 - pdu * gd
    )},${Math.floor(250 - pdu * bd)})`;
    traces[pdu].hovertext.push(
      `Type: ${
        group < irc ? "Current" : "Future"
      }<br>PDU: ${pdu}<br>Subevent: ${subeventNo % nse}`
    );

    if (group === 0) {
      annotations.push({
        text: `Event ${currentEvent}`,
        x: currentEvent * (nse + 1) + (nse - 1) / 2,
        y: -0.1,
        align: "center",
        showarrow: false,
        font: {
          family: fontFamily,
          size: 15,
        },
      });
    }
  }

  function limit(val, min, max) {
    return Number.isNaN(val) ? val : Math.min(Math.max(val, min), max);
  }

  function readInputs() {
    const params = {
      valid: true,
      nse: limit(parseInt(inNse.value), 1, 15),
      bn: limit(parseInt(inBn.value), 1, 7),
      irc: limit(parseInt(inIrc.value), 1, 15),
      pto: limit(parseInt(inPto.value), 0, 15),
      numBis: limit(parseInt(inNumBis.value), 1, 15),
      phy: parseInt(inPhy.value),
      maxPdu: limit(parseInt(inMaxPdu.value), 1, 251),
      packing: parseInt(inPacking.value),
      isoInterval: parseInt(inIsoInterval.value),
      sduInterval: parseInt(inSduInterval.value),
      framing: parseInt(inFraming.value),
    };

    if (Object.values(params).some((val) => Number.isNaN(val))) {
      params.valid = false;
      return params;
    }

    inNse.value = params.nse;
    inBn.value = params.bn;
    inIrc.value = params.irc;
    inPto.value = params.pto;
    inNumBis.value = params.numBis;
    inMaxPdu.value = params.maxPdu;

    return params;
  }

  function validateInputs(params) {
    if (!params.valid) {
      return "All input parameters should be entered";
    }

    if (params.nse % params.bn) {
      return "BN must be an integer divisor of NSE";
    }

    const gc = params.nse / params.bn;
    if (gc > params.irc) {
      if (!params.pto) {
        return "PTO must be greater than 0 when (NSE / BN) > IRC";
      }
    } else if (gc == params.irc) {
      if (params.pto) {
        return "PTO must be 0 when (NSE / BN) == IRC";
      }
    } else {
      return "(NSE / BN) must be smaller than or equal to IRC";
    }

    if (params.isoInterval % 1250) {
      return "ISO interval must be an integer multiple of 1250us";
    }

    if (params.isoInterval < 5000 || params.isoInterval > 4000000) {
      return "ISO interval must be between 5000us and 4000000us";
    }

    if (params.sduInterval < 255 || params.sduInterval > 1048575) {
      return "SDU interval must be between 255us and 1048575us";
    }

    if (params.isoInterval < params.sduInterval && !params.framing) {
      return "Framing must be enabled when ISO interval is smaller that SDU interval";
    }

    return null;
  }

  function showEvents() {
    const fontFamily =
      "system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, Liberation Sans, sans-serif";
    const numberOfEvents = 4;
    const params = readInputs();
    const err = validateInputs(params);
    const guiTable = document.getElementById("gui-table");
    const eventLayout = document.getElementById("event-layout");
    guiTable.replaceChildren();
    eventLayout.replaceChildren();
    if (err) {
      const errorElement = document.createElement("div");
      errorElement.setAttribute("role", "alert");
      errorElement.setAttribute("aria-live", "assertive");
      errorElement.style.cssText =
        "color:red;font-family:monospace;font-size:15px;margin-top:30px;padding:10px;border:1px solid red;border-radius:4px;background-color:#ffeaea;";
      errorElement.innerHTML = `<strong>Validation Error:</strong> ${err}`;
      guiTable.appendChild(errorElement);

      // Announce error to screen readers
      announceToScreenReader(`Validation error: ${err}`, "assertive");
      return;
    }

    const gc = Math.floor(params.nse / params.bn);
    const ptoGroups = gc - params.irc;
    const numberOfPdus =
      (numberOfEvents - 1) * params.bn +
      ptoGroups * params.bn * params.pto +
      params.bn +
      4;

    let mpt;
    if (params.phy === 1) {
      mpt = (10 + params.maxPdu) * 8;
    } else if (params.phy === 2) {
      mpt = (11 + params.maxPdu) * 4;
    } else {
      mpt = (10 + params.maxPdu) * 64 + 80;
    }

    const minSubInterval = 32 + params.isoInterval / 500;
    let subInterval, spacing;
    if (params.packing === 0) {
      subInterval = mpt + 150;
      if (subInterval < minSubInterval) {
        subInterval = minSubInterval;
      }
      spacing = subInterval * params.nse;
    } else {
      spacing = mpt + 150;
      subInterval = spacing * params.numBis;
      if (subInterval < minSubInterval) {
        subInterval = minSubInterval;
      }
    }

    const syncDelay =
      (params.numBis - 1) * spacing + (params.nse - 1) * subInterval + mpt;
    let transportLatency;
    if (params.framing === 1) {
      transportLatency =
        syncDelay +
        params.pto * (gc - params.irc) * params.isoInterval +
        params.isoInterval +
        params.sduInterval;
    } else {
      transportLatency =
        syncDelay +
        (params.pto * (gc - params.irc) + 1) * params.isoInterval -
        params.sduInterval;
    }

    const annotations = [];
    const traces = [];
    for (let pdu = 0; pdu < numberOfPdus; pdu++) {
      traces.push(getDefaultTrace());
    }

    for (
      let subeventNo = 0;
      subeventNo < numberOfEvents * params.nse;
      subeventNo++
    ) {
      setSubeventTrace(
        traces,
        annotations,
        numberOfPdus,
        subeventNo,
        params.nse,
        params.bn,
        params.irc,
        params.pto,
        fontFamily
      );
    }

    const layout = {
      autosize: true,
      showlegend: false,
      textangle: 0,
      yaxis: {
        range: [-0.25, 1.25],
        visible: false,
        fixedrange: true,
      },
      xaxis: {
        range: [-1, numberOfEvents * params.nse + numberOfEvents - 1],
        visible: false,
      },
      margin: {
        b: 0,
        l: 0,
        r: 0,
        t: 0,
      },
      annotations: annotations,
      plot_bgcolor: "rgba(0,0,0,0)",
      paper_bgcolor: "rgba(0,0,0,0)",
      modebar: {
        color: "rgb(56, 103, 113)",
        activecolor: "rgb(0, 169, 206)",
        bgcolor: "rgba(0,0,0,0)",
      },
    };

    const config = {
      modeBarButtonsToRemove: [
        "toImage",
        "select2d",
        "lasso2d",
        "zoomIn2d",
        "zoomOut2d",
        "autoScale2d",
      ],
      displaylogo: false,
      responsive: true,
    };

    Plotly.newPlot("event-layout", traces, layout, config);
    const dragLayer = document.getElementsByClassName("nsewdrag")[0];
    document.getElementById("event-layout").on("plotly_hover", function (data) {
      dragLayer.style.cursor = "default";
    });

    var values = [
      ["ISO Interval [ms]", "Sync Delay [ms]", "Transport Latency [ms]"],
      [params.isoInterval / 1000, syncDelay / 1000, transportLatency / 1000],
    ];

    var data = [
      {
        type: "table",
        columnwidth: [2, 1],
        header: {
          values: [["<b>Parameter</b>"], ["<b>Value</b>"]],
          align: "center",
          fill: { color: "rgb(106, 176, 222)" },
          font: { family: fontFamily, size: 12, color: "white" },
          height: 20,
        },
        cells: {
          values: values,
          align: "center",
          font: { family: fontFamily, size: 14, color: ["black", "black"] },
          fill: { color: ["rgb(231, 242, 250)", "rgb(231, 242, 250)"] },
          height: 28,
        },
      },
    ];

    const table_layout = {
      autosize: true,
      margin: {
        b: 1,
        l: 1,
        r: 1,
        t: 26,
      },
      plot_bgcolor: "rgba(0,0,0,0)",
      paper_bgcolor: "rgba(0,0,0,0)",
      dragmode: false,
      height: 150,
      minreducedwidth: 250,
    };

    const table_config = {
      modeBarButtonsToRemove: [
        "toImage",
        "select2d",
        "lasso2d",
        "zoomIn2d",
        "zoomOut2d",
        "autoScale2d",
      ],
      displaylogo: false,
      responsive: true,
      staticPlot: true,
    };

    Plotly.newPlot("gui-table", data, table_layout, table_config);

    // Accessibility: Announce successful calculation completion
    announceToScreenReader(
      `BIS calculations updated. ISO Interval: ${
        params.isoInterval / 1000
      } milliseconds, Sync Delay: ${(syncDelay / 1000).toFixed(
        2
      )} milliseconds, Transport Latency: ${(transportLatency / 1000).toFixed(
        2
      )} milliseconds.`
    );
  }
</script>
